#include <iostream>
#include <cstring>
#include <chrono>
#include <glm/mat4x4.hpp>
#include <glm/gtx/transform.hpp>
#include "VK_UniformBuffer.h"
#include "VK_Context.h"
#include "VK_Image.h"
#include "VK_Texture.h"
#include "VK_Pipeline.h"
#include "VK_DynamicState.h"
#include "VK_Window.h"
#include "../Node.h"
#include "../ObjectFactory.h"

using namespace std;

class meshtess: public Node
{
public:
    meshtess();
protected:
    RunningState run()override;
    uint32_t updateUniformBufferData(char *&data, uint32_t size);

    bool checkDevice(const VkPhysicalDeviceFeatures &feature) override
    {
        return feature.tessellationShader && feature.fillModeNonSolid;
    }
protected:
    VK_Context *context = nullptr;
    VK_Pipeline *pipeline = nullptr;
};

meshtess::meshtess()
{
    nodeName = "meshtess";
}

RunningState meshtess::run()
{
    auto window = VK_Window::getInstance()->getWindow();
    auto title = getNodeTitle();
    glfwSetWindowTitle(window, title.data());

    context = createVkContext();
    context->setPhysicalDeviceFeaturesCallback(std::bind(&meshtess::checkDevice, this,
                                                         std::placeholders::_1));

    VkPhysicalDeviceFeatures deviceFeatures{};
    deviceFeatures.tessellationShader = VK_TRUE;
    deviceFeatures.fillModeNonSolid = VK_TRUE;
    context->setLogicalDeviceFeatures(deviceFeatures);

    VK_Context::VK_Config vkConfig;
    bool ok = context->initVulkanDevice(vkConfig);
    if (!ok)
        return RunningState_Failed;

    auto shaderSet = context->createShaderSet();
    shaderSet->addShader("../shader/geom-mesh-tess/vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
    shaderSet->addShader("../shader/geom-mesh-tess/frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
    shaderSet->addShader("../shader/geom-mesh-tess/tesc.spv", VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT);
    shaderSet->addShader("../shader/geom-mesh-tess/tese.spv",
                         VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT);

    shaderSet->appendVertexAttributeDescription(0, sizeof (float) * 3, VK_FORMAT_R32G32B32_SFLOAT, 0);
    shaderSet->appendVertexAttributeDescription(1, sizeof (float) * 2, VK_FORMAT_R32G32_SFLOAT,
                                                sizeof(float) * 3);
    shaderSet->appendVertexAttributeDescription(2, sizeof (float) * 3, VK_FORMAT_R32G32B32_SFLOAT,
                                                sizeof(float) * 5);

    shaderSet->appendVertexInputBindingDescription(8 * sizeof(float), 0, VK_VERTEX_INPUT_RATE_VERTEX);

    VkDescriptorSetLayoutBinding uniformBinding = VK_ShaderSet::createDescriptorSetLayoutBinding(0,
                                                                                                 VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT);
    shaderSet->addDescriptorSetLayoutBinding(uniformBinding);

    if (!shaderSet->isValid()) {
        std::cerr << "invalid shaderSet" << std::endl;
        shaderSet->release();
        context->release();
        return RunningState_Failed;
    }

    auto ubo = shaderSet->addUniformBuffer(0, sizeof(float) * 17);
    ubo->setWriteDataCallback([&](char *&data, uint32_t size)->uint32_t{return updateUniformBufferData(data, size);});

    context->initVulkanContext();
    pipeline = context->createPipeline(shaderSet);

    auto tessellationCreateInfo = VK_Pipeline::createPipelineTessellationStateCreateInfo(32);
    tessellationCreateInfo.patchControlPoints = 3;

    auto inputAssmly = pipeline->getInputAssemblyStateCreateInfo();
    inputAssmly.topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;

    pipeline->setInputAssemblyStateCreateInfo(inputAssmly);
    pipeline->setTessellationStateCreateInfo(tessellationCreateInfo);

    auto rasterCreateInfo = pipeline->getRasterizationStateCreateInfo();
    rasterCreateInfo.polygonMode = VK_POLYGON_MODE_LINE;
    pipeline->setRasterizationStateCreateInfo(rasterCreateInfo);

    pipeline->create();
    auto buffer = context->createVertexBuffer("../model/pug.obj", true);
    pipeline->addRenderBuffer(buffer);

    context->createCommandBuffers();

    auto result = context->run();
    setAverageFps(context->getFps());
    context->release();
    return result;
}

uint32_t meshtess::updateUniformBufferData(char *&data, uint32_t size)
{
    static auto startTime = std::chrono::high_resolution_clock::now();
    auto currentTime = std::chrono::high_resolution_clock::now();
    float time = std::chrono::duration<float, std::chrono::seconds::period>
                 (currentTime - startTime).count();
    glm::mat4 model = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f));
    model *= glm::rotate(glm::mat4(1.0f), time * glm::radians(30.0f), glm::vec3(0.0f, 1.0f, 0.0f));
    auto view = glm::lookAt(glm::vec3(0.0f, 4.0f, 0.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f,
                                                                                                0.0f, 1.0f));
    auto window = VK_Window::getInstance()->getWindow();
    int32_t width, height;
    glfwGetFramebufferSize(window, &width, &height);

    auto proj = glm::perspective(glm::radians(45.0f),
                                 (float)width / (float)height, 0.1f, 10.0f);

    proj[1][1] *= -1;

    model = proj * view * model;
    memcpy(data, &model[0][0], size);

    time = sin(time);
    memcpy(data + sizeof(float) * 16, (void *)&time, sizeof(float));
    return 17 * sizeof(float);
}

REGISTER_OBJECT(Node, meshtess)
